1
|
|
|
'use strict'; |
2
|
|
|
var token = '%[a-f0-9]{2}'; |
3
|
|
|
var singleMatcher = new RegExp(token, 'gi'); |
4
|
|
|
var multiMatcher = new RegExp('(' + token + ')+', 'gi'); |
5
|
|
|
|
6
|
|
|
function decodeComponents(components, split) { |
7
|
|
|
try { |
8
|
|
|
// Try to decode the entire string first |
9
|
|
|
return decodeURIComponent(components.join('')); |
10
|
|
|
} catch (err) { |
|
|
|
|
11
|
|
|
// Do nothing |
12
|
|
|
} |
13
|
|
|
|
14
|
|
|
if (components.length === 1) { |
15
|
|
|
return components; |
16
|
|
|
} |
17
|
|
|
|
18
|
|
|
split = split || 1; |
19
|
|
|
|
20
|
|
|
// Split the array in 2 parts |
21
|
|
|
var left = components.slice(0, split); |
22
|
|
|
var right = components.slice(split); |
23
|
|
|
|
24
|
|
|
return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right)); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
function decode(input) { |
28
|
|
|
try { |
29
|
|
|
return decodeURIComponent(input); |
30
|
|
|
} catch (err) { |
31
|
|
|
var tokens = input.match(singleMatcher); |
32
|
|
|
|
33
|
|
|
for (var i = 1; i < tokens.length; i++) { |
34
|
|
|
input = decodeComponents(tokens, i).join(''); |
35
|
|
|
|
36
|
|
|
tokens = input.match(singleMatcher); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
return input; |
40
|
|
|
} |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
function customDecodeURIComponent(input) { |
44
|
|
|
// Keep track of all the replacements and prefill the map with the `BOM` |
45
|
|
|
var replaceMap = { |
46
|
|
|
'%FE%FF': '\uFFFD\uFFFD', |
47
|
|
|
'%FF%FE': '\uFFFD\uFFFD' |
48
|
|
|
}; |
49
|
|
|
|
50
|
|
|
var match = multiMatcher.exec(input); |
51
|
|
|
while (match) { |
52
|
|
|
try { |
53
|
|
|
// Decode as big chunks as possible |
54
|
|
|
replaceMap[match[0]] = decodeURIComponent(match[0]); |
55
|
|
|
} catch (err) { |
56
|
|
|
var result = decode(match[0]); |
57
|
|
|
|
58
|
|
|
if (result !== match[0]) { |
59
|
|
|
replaceMap[match[0]] = result; |
60
|
|
|
} |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
match = multiMatcher.exec(input); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
// Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else |
67
|
|
|
replaceMap['%C2'] = '\uFFFD'; |
68
|
|
|
|
69
|
|
|
var entries = Object.keys(replaceMap); |
70
|
|
|
|
71
|
|
|
for (var i = 0; i < entries.length; i++) { |
72
|
|
|
// Replace all decoded components |
73
|
|
|
var key = entries[i]; |
74
|
|
|
input = input.replace(new RegExp(key, 'g'), replaceMap[key]); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
return input; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
module.exports = function (encodedURI) { |
81
|
|
|
if (typeof encodedURI !== 'string') { |
82
|
|
|
throw new TypeError('Expected `encodedURI` to be of type `string`, got `' + typeof encodedURI + '`'); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
try { |
86
|
|
|
encodedURI = encodedURI.replace(/\+/g, ' '); |
87
|
|
|
|
88
|
|
|
// Try the built in decoder first |
89
|
|
|
return decodeURIComponent(encodedURI); |
90
|
|
|
} catch (err) { |
91
|
|
|
// Fallback to a more advanced decoder |
92
|
|
|
return customDecodeURIComponent(encodedURI); |
93
|
|
|
} |
94
|
|
|
}; |
95
|
|
|
|